package battleGame;

import java.util.Vector;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

public class GameLogic 
{
	LinkedList< WarriorAction > warriorActionList;
	
	Army blueTeam;
	Army redTeam;
	
	Vector< Warrior > army1WarriorList;
	Vector< Warrior > army2WarriorList;
	Vector< GameObject > allGameObjectList;
	double redTime, blueTime;
	
	World myWorld;
		
	public GameLogic(World w)
	{
		myWorld = w;
	   //create armies
		blueTeam = new Army( 5, 5, 1 );
		redTeam = new Army( 5, 5, 2 );
		fillWarriorList();
		redTime = Constant.winTime;
		blueTime = Constant.winTime;
	}
	
	//Esta funcion nos va a dar la cantidad de guerreros en nuestras 3 listas
	//lista de guerreros por cada equipo y una general
	public void fillWarriorList()
	{
		army1WarriorList = blueTeam.getCharacterList();
		army2WarriorList = redTeam.getCharacterList();
		allGameObjectList = new Vector< GameObject >();
		allGameObjectList.addAll( army1WarriorList );
		allGameObjectList.addAll( army2WarriorList );
		allGameObjectList.add( myWorld.flag );
	}
	
	//Funcion utilizada en reportSight()
	//Revisa el campo visual de un guerrero, agnadiendo el reporte al reporte por ejercito y al reporte
	//de ese guerrero en particular
	private void reviewArmySight( Warrior origin, Vector< GameObject > targetArmyList, Vector< GameObject > warriorReport, Vector< GameObject > armyReport )
	{
		if( warriorReport == null )
			warriorReport = new Vector< GameObject >( targetArmyList.size() );
		Utils.Assert( armyReport != null, "ArmyReport should be not null" );
		for( Iterator< GameObject > iterTarget = targetArmyList.iterator(); iterTarget.hasNext();  )
		{
			Warrior warriorTarget = null;
			GameObject target = null;
			target = (GameObject)iterTarget.next();
			if( target instanceof Warrior )				
				warriorTarget = (Warrior)target;
//			else if( origin.team == 2 )
//				System.out.print( "" + origin.team + ". punto donde se revisa distancia con la bandera\n" );
			if( origin != target )
				if( Utils.fIsObjectInSight( origin , target ) )
				{
					warriorReport.add( target );
					//the warrior is added to the army report, only if it's not on the same team and it's not been added before
					if( ( warriorTarget == null || origin.team != warriorTarget.team ) && !armyReport.contains( target ) )
						armyReport.add( target );
				}
		}		
	}
	
	//Funcion que se encarga de todo lo que tiene que ver con campo de vision
	//Encuentra campo de vision por cada guerrero
	//Suma todos los reportes de guerreros y lo suma a un reporte de ejercito
	//Ademas envia ese reporte
	public void reportSight()
	{
		//TODO: add the search of the flag
		
		Vector< GameObject > blueReport = new Vector< GameObject >( army2WarriorList.size() + 1 );
		Vector< GameObject > redReport = new Vector< GameObject >( army1WarriorList.size() + 1 );
		
		Warrior origin = null;
		for( Iterator< Warrior > iterOrigin = army1WarriorList.iterator(); iterOrigin.hasNext();  )
		{
			origin = (Warrior)iterOrigin.next();
			Vector< GameObject > warriorReport = new Vector< GameObject >( allGameObjectList.size() );
			
			reviewArmySight( origin, allGameObjectList, warriorReport, blueReport );			
			origin.setActualSight( warriorReport );
		}
		blueTeam.setActualSight( blueReport );
		
		for( Iterator< Warrior > iterOrigin = army2WarriorList.iterator(); iterOrigin.hasNext();  )
		{
			origin = (Warrior)iterOrigin.next();
			Vector< GameObject > warriorReport = new Vector< GameObject >( allGameObjectList.size() );
			
			reviewArmySight( origin, allGameObjectList, warriorReport, redReport );			
			origin.setActualSight( warriorReport );
		}
		redTeam.setActualSight( redReport );
	}
	
	//Simplemente llama a la funcion update de cada ejercito
	public void updateArmy()
	{
		blueTeam.update();
		redTeam.update();
	}
	
	//Solicita a cada guerrero su accion por el turno
	//Llama a la funcion actionTurn() de cada uno
	//Todas las acciones se guardan en warriorActionList
	public void updateWarriors()
	{	
		warriorActionList = new LinkedList< WarriorAction >();
		Warrior warrior;
		for( Iterator< Warrior > iterOrigin = army1WarriorList.iterator(); iterOrigin.hasNext();  )
		{
			warrior = (Warrior)iterOrigin.next();
			warriorActionList.add( warrior.actionTurn() );
		}
		for( Iterator< Warrior > iterOrigin = army2WarriorList.iterator(); iterOrigin.hasNext();  )
		{
			warrior = (Warrior)iterOrigin.next();
			warriorActionList.add( warrior.actionTurn() );
		}
	}
	
	//Todas las acciones antes decididas, se procesan.
	//Se avalan movimientos y se calculan dagnos a otros guerreros
	//se reporta el dagno a cada guerrero y una suma de dagnos a cada ejercito
	public void processActions()
	{
		Vector< DamageToken > blueDamageReport = new Vector< DamageToken >( army1WarriorList.size() );
		Vector< DamageToken > redDamageReport = new Vector< DamageToken >( army2WarriorList.size() );
		ListIterator< WarriorAction > itr = warriorActionList.listIterator();
		WarriorAction warriorAction;
		while( itr.hasNext() )
		{
			warriorAction = itr.next();
			switch( warriorAction.action )
			{
			case ATTACKARROW:
			case ATTACKSWORD:
/*				Iterator< Warrior > itrTarget;
				if( warriorAction.warrior.team == 1 )
					itrTarget = army2WarriorList.iterator();
				else
					itrTarget = army1WarriorList.iterator();
				Warrior target;
				while( itrTarget.hasNext() )
				{
					target = itrTarget.next();
					if( warriorAction.warrior == target )
						continue;
					
//					double positionDistance = Vector2.minus( warriorAction.warrior.position, target.position ).magnitude();
					
//					if( positionDistance > warriorAction.warrior.attackRadius )
//					{
//						Utils.Assert( true, "Sword Attack distance is bigger than allowed" + warriorAction.warrior );
//						continue;
//					}
					
//					if( positionDistance < ( target.spaceRadius + Constant.swordAttackRadius ) )
*/
					if( Utils.fIsWarriorInAttackRange( warriorAction.warrior, warriorAction.target ) )
					{
						DamageToken damageToken = new DamageToken();
						warriorAction.target.setDamage( warriorAction.warrior.attackDamage );
						damageToken.affectedGuy = warriorAction.target;
						damageToken.damageGiven = warriorAction.warrior.attackDamage;
						if( warriorAction.target.team == 1 )
							blueDamageReport.add( damageToken );
						else if( warriorAction.target.team == 2 )
							redDamageReport.add( damageToken );
						else
							Utils.Assert( true, "This condition was not expected" );
					}
//				}
				break;
				
				/*
				if( warriorAction.warrior == warriorAction.target )
					continue;
				
				double positionDistance = Vector2.minus( warriorAction.warrior.position, warriorAction.target.position ).magnitude();
				if( positionDistance > warriorAction.warrior.attackRadius || positionDistance < warriorAction.warrior.spaceRadius )
				{
					Utils.Assert( true, "Arrow Attack distance is not allowed" + warriorAction.warrior + ", " + positionDistance );
					continue;
				}
				
				//Calculating the damage of the archer
				double space = warriorAction.warrior.attackRadius - warriorAction.warrior.spaceRadius;
				int damage = warriorAction.warrior.attackDamage - (int)( ( positionDistance - warriorAction.warrior.spaceRadius ) * ( (double)warriorAction.warrior.attackDamage ) / ( 2 * space ) );

				DamageToken damageToken = new DamageToken();
				warriorAction.target.setDamage( damage );
				damageToken.affectedGuy = warriorAction.target;
				damageToken.damageGiven = damage;
				
				if( warriorAction.target.team == 1 )
					blueDamageReport.add( damageToken );
				else if( warriorAction.target.team == 2 )
					redDamageReport.add( damageToken );
				else
					Utils.Assert( true, "This condition was not expected" );
				break;
	*/			
			case MOVE:
				Vector2 finalPoint;
				double distance = Vector2.minus( warriorAction.point, warriorAction.warrior.position ).magnitude();
				if( distance > warriorAction.warrior.speed )
				{
					Utils.Assert( true, "Move speed is bigger than allowed" + warriorAction.warrior + ", " + distance );
					continue;					
				}
				finalPoint = solveCollision( warriorAction.warrior, warriorAction.point );
				warriorAction.warrior.setPosition( finalPoint );
				break;
				
			case ROTATE:
				if( warriorAction.warrior == null )
				{
					System.out.print( "Error con warrior, era nulo" );
				}
				warriorAction.warrior.lookAt.rotate( warriorAction.point.x );
				break;
				
			default:
				Utils.Assert( true, "This case should not be happening" );
				break;
			}
		}
		
		blueTeam.setDamage( blueDamageReport );
		redTeam.setDamage( redDamageReport );
	}
	
	//Funcion que sirve para encontrar una posicion donde el guerrero no tenga colision con ningun otro
	//recibe de parametro el guerrero y su punto tentativo para moverse
	//el codigo revisara que no colisione con ningun otro, y si lo hace, lo mueve
	//el vector resultante es su posicion donde no colisione con nadie mas
	private Vector2 solveCollision( Warrior warrior, Vector2 point )
	{
		boolean fCollisionFound = true;
		if( warrior == null || point == null )
		{
			
			Utils.Assert( false, "null pointer in solve Collision" );
			return null;
		}
//		while( fCollisionFound )
		{
			fCollisionFound = false;
			double distance;
			for( Iterator< GameObject > iterTarget = allGameObjectList.iterator(); iterTarget.hasNext();  )
			{
				GameObject target = null;
				target = (GameObject)iterTarget.next();
				if( target == warrior || target == myWorld.flag )
					continue;
				
				Vector2 warriorDirection = Vector2.minus( point, target.position );
				distance = warriorDirection.magnitude();
				double minimumDistance = warrior.spaceRadius + target.spaceRadius ; 
				if( distance < minimumDistance )
				{
					warriorDirection.normalize();
					Vector2 moveVector = Vector2.mul( warriorDirection, ( minimumDistance - distance ) );
					point = Vector2.suma( point, moveVector );
					fCollisionFound = true;
					break;
				}
			}
		}
		if( fCollisionFound )
			return point;
		return point;
	}
	
	//World llama a esta funcion cada turno para ser procesada
	public void update()
	{
//		System.out.println( "Esto es ejecutado" );

		fillWarriorList();
		reportSight();
		updateArmy();
		updateWarriors();
		processActions();
		processWinLogic();
		//recalculate route every frame
		/*
		AStarNode node=myWorld.startSearch(myWorld.worldMatrix
												[myWorld.platoon1[1].row][myWorld.platoon1[1].col],myWorld.worldMatrix[3][9]);
		double[] coords = new double[2];
		coords = myWorld.getCoordsFromID(node.id);
		//update character position
		myWorld.platoon1[1].row=(int)coords[0];
		myWorld.platoon1[1].col=(int)coords[1];
		System.out.println("move to "+node.id);
		*/
		
	}
	
	public Vector< GameObject > getRendererList()
	{
		Vector< GameObject > renderList = new Vector< GameObject >();
		renderList.addAll( this.blueTeam.getRenderList() );
		renderList.addAll( this.redTeam.getRenderList() );
		return renderList;
	}
	
	void processWinLogic()
	{
		if( army1WarriorList.size() <= 0 )
		{
			System.out.print( "Gana el equipo 2, todos los miembros de 1 han muerto\n" );
			return;
		}
		if( army2WarriorList.size() <= 0 )
		{
			System.out.print( "Gana el equipo 1, todos los miembros de 2 han muerto\n");
			return;
		}
		if( blueTime < 0 )
		{
			System.out.print( "Gana el equipo 1, controlo la bandera por " + Constant.winTime + " segundos\n");
			return;			
		}
		if( redTime < 0 )
		{
			System.out.print( "Gana el equipo 2, controlo la bandera por " + Constant.winTime + " segundos\n");
			return;			
		}
		for( int i = 0; i < army1WarriorList.size(); i ++ )
		{
			if( Vector2.minus( army1WarriorList.get(i).position, myWorld.flag.position ).magnitude() < Constant.controlFlagDistance )
			{
				blueTime -= Time.getDeltaTime();
				break;
			}
		}
		for( int i = 0; i < army2WarriorList.size(); i ++ )
		{
			if( Vector2.minus( army2WarriorList.get(i).position, myWorld.flag.position ).magnitude() < Constant.controlFlagDistance )
			{
				redTime -= Time.getDeltaTime();
				break;
			}
		}
		System.out.print( "1. " + (int)blueTime + "s - 2. " + (int)redTime + "s \n");
	}
}
